using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using T.FAS.Runtime.Base.Entity;
using T.FAS.Runtime.DataAccess.Common;
using T.FAS.Runtime.DataAccess.SPI;
using T.FAS.Runtime.Metadata.Entity.DataModel;
using T.FAS.Runtime.StateMachine.Service;
using T.FAS.Runtime.Expression.Service;
using T.ServiceFramework.TaskCenter.Spi;

namespace T.FAS.Runtime.DataAccess.Engine
{
    /// <summary>
    /// 统一数据访问服务状态机插件
    /// </summary>
    public class StateMachinePlugin : IDataAccessPlugin
    {
        #region Fields & Properties
        private readonly IDACLogService s_localLogService = DACLogFactory.GetDACLogService();
        private const string _pluginName = "状态机插件";
        private InvokeContext _invokeContext;
        private List<StateMachineMetadata> _machines;
        private Dictionary<StateMachineMetadata, string> _stateChangeLog = new Dictionary<StateMachineMetadata, string>();

        /// <summary>
        /// 插件注入点
        /// </summary>
        public int InjectionPoint
        {
            get
            {
                var injectionPoint = SPI.InjectionPoint.Entry
                                    | SPI.InjectionPoint.BeforeExecuteSQL
                                    | SPI.InjectionPoint.Finish;
                return (int)injectionPoint;
            }
        }
        /// <summary>
        /// 插件注入方法类型
        /// </summary>
        public int InjectionMethodType
        {
            get
            {
                var injectionType = SPI.InjectionMethodType.Create | SPI.InjectionMethodType.Update;
                return (int)injectionType;
            }
        }
        public List<string> MetaType => new List<string> { DataModelConst.CDOMetaType };
        public string PluginCode => PluginConst.StateMachine.Code;
        public bool IsCanRepeatExecute => true;
        public InvokeContext InvokeContext
        {
            get
            {
                return _invokeContext;
            }
        }
        #endregion

        public void BuildInvokeContext(InvokeContext invokeContext)
        {
            this._invokeContext = invokeContext;
        }

        /// <summary>
        /// 重载
        /// </summary>
        /// <param name="injectMethodType"></param>
        /// <param name="injectPoint"></param>
        /// <returns></returns>
        public ActionResult Execute(InjectionMethodType injectMethodType, InjectionPoint injectPoint, DataAccessPluginExtParam extExecuteData = null)
        {
            if (!ValidateInjectPoint(injectMethodType, injectPoint))
                return new ActionResult
                {
                    Result = true,
                    Message = "不符合当前插件执行时机， 不予执行",
                };

            if (injectPoint == SPI.InjectionPoint.Entry)
                return ValidInput(_invokeContext);
            if (injectPoint == SPI.InjectionPoint.BeforeExecuteSQL)
                return JudgeIfStateChangedAndExecuteBeforeSaveEvent(_invokeContext, injectMethodType);
            if (injectPoint == SPI.InjectionPoint.Finish)
                return FireAfterSaveEvent(_invokeContext, injectMethodType, extExecuteData);
            else
                return default;
        }

        #region Private Funcs
        /// <summary>
        /// 新增，更新时调用状态机方法，执行保存前的调用状态机Rules的数据Check
        /// </summary>
        /// <param name="invokeContext"></param>
        private ActionResult ValidInput(InvokeContext invokeContext)
        {
            ActionResult ExecuteResult = new ActionResult
            {
                ActionErrorHandleType = ActionErrorHandleType.Throw,
            };
            if (!this.IsCanRepeatExecute && invokeContext.ExecuteLogs.Count > 0)
                return new ActionResult
                {
                    PluginCode = PluginCode,
                    Result = true,   //插件类型不匹配时，不执行插件逻辑
                    Message = $"{_pluginName}插件不允许重复执行",
                };
            try
            {
                if (!MetaType.Contains(invokeContext.MetaType))
                {
                    ExecuteResult = new ActionResult
                    {
                        PluginCode = PluginCode,
                        Result = true   //插件类型不匹配时，不执行插件逻辑
                    };
                    return ExecuteResult;
                }
                var flag = ValidateContext(invokeContext, out ActionResult actionResult);
                if (!flag)
                {
                    return actionResult;
                }
                var CDO = invokeContext.SourceMeta as CompositeDataObject;
                _machines = InitCDOStateMachine(CDO);
                try
                {
                    foreach (var machine in _machines)
                    {
                        invokeContext.SourceData.FillPkColumn(CDO.RootCompositeNodeObject);
                        var result = StateMachineService.IsValid(machine, invokeContext.SourceData);
                        if (!result.IsSuccess)
                        {
                            actionResult.Result = false;
                            actionResult.Message += $"{result.FailReason}";
                        }
                    }
                }
                catch (Exception e)
                {
                    throw new FasException(DataAccessErrorCode.PlugIn.ChangeLog.Error, string.Format(CommonStringClassEngine.StateMachinePlugin_ValidInput,_pluginName,CDO.Code,e.Message), e);
                }
                if (actionResult.Result)    //执行成功记录日志
                    invokeContext.ExecuteLogs.Add(actionResult);
                return actionResult;
            }
            catch (Exception e)
            {
                ActionResult actionResult = new ActionResult
                {
                    PluginCode = PluginCode,
                    Result = false,
                    Exception = e,
                    Message = $"{_pluginName}执行异常, Msg:{e.Message}",
                    ActionErrorHandleType = ActionErrorHandleType.Throw,
                };
                return actionResult;
            }
        }

        /// <summary>
        /// 比较当前TRecord的是否存在状态变更，并执行保存前事件
        /// </summary>
        /// <param name="invokeContext"></param>
        /// <returns></returns>
        private ActionResult JudgeIfStateChangedAndExecuteBeforeSaveEvent(InvokeContext invokeContext, InjectionMethodType injectMethodType)
        {
            if (invokeContext == null)
                throw new FasException(DataAccessErrorCode.PlugIn.ChangeLog.Error, string.Format(CommonStringClassEngine.StateMachinePlugin_JudgeIfStateChangedAndExecuteBeforeSaveEvent));
            if (_machines == null || _machines.Count == 0)
                return new ActionResult
                {
                    Result = true,
                    Message = $"当前CDO:{invokeContext.SourceMeta.MetaDataCode}不存在状态机",
                };
            _stateChangeLog = new Dictionary<StateMachineMetadata, string>();    //防止多次执行，每次清空
            foreach (var machine in _machines)
            {
                var @event = StateChangeCompare(machine, invokeContext, injectMethodType, out TState newState);
                string eventKey = @event?.Key;
                //如果包含状态变更
                if (!string.IsNullOrEmpty(eventKey))
                {
                    //如果事件包含前置条件，提前进行校验
                    if (!string.IsNullOrEmpty(@event.PreconditionExpressionJson))
                    {
                        var fasContext = new FASContext
                        {
                            CurrentRecord = invokeContext.SourceData,
                            PKColumnName = invokeContext.SourceData?.PKColumnName,
                        };
                        Dictionary<string, object> expParam = new Dictionary<string, object>();
                        var exprExecResult = ExprService.GetInstance.Execute(@event.PreconditionExpressionJson, fasContext, expParam);
                        if (exprExecResult.State == Expression.Entity.ResultState.Fail)
                        {
                            var errorMsg = $"{machine?.Code}事件:{@event.Key}的前置表达式条件执行失败！msg:{exprExecResult.ErrMsg}";
                            return new ActionResult
                            {
                                Result = false,
                                ActionErrorHandleType = ActionErrorHandleType.Throw,
                                Message = errorMsg,
                            };
                        }
                        else
                        {
                            if (!bool.TryParse(exprExecResult.Result?.ToString(), out bool boolResult) || !boolResult)
                            {
                                var errorMsg = $"不满足事件:{@event.Key}的前置表达式条件，不允许保存";
                                return new ActionResult
                                {
                                    Result = false,
                                    ActionErrorHandleType = ActionErrorHandleType.Throw,
                                    Message = errorMsg,
                                };
                            }
                        }
                    }
                    _stateChangeLog[machine] = eventKey;
                    try
                    {
                        if (invokeContext.ExtProperties != null && invokeContext.ExtProperties.ContainsKey(DACConst.CDO.IsBatchUpdate))
                        {
                            //处理批量编辑时状态机变更逻辑
                            if (invokeContext.ExtProperties[DACConst.CDO.IsBatchUpdate]?.ToString().ToLower() == "true")
                            {
                                //批量更新切换状态时，需要内置执行FireStateChanging事件动作，将状态切换到To状态。
                                /*warn：由Action端调用刷新Record方法，用前端只包含ID与ChangeSet的TRecord刷新从DB取出的OldTRecord
                                 * 此方法会执行状态字段的刷新赋值，此时OldTRecord中的ChangeSet中会包含该状态字段的变更项，从而使用
                                 * 该变更项的OldState值将当前TRecord的状态值刷回旧值，以用于执行状态机触发FireStateChanging事件
                                 */
                                try
                                {
                                    var changeItem = invokeContext.SourceData.ChangeSet.ChangeSetItems.FirstOrDefault(item => item.ColumnCode == machine.CDOColKey);
                                    invokeContext.SourceData.SetValue(machine.CDOColKey, changeItem.OldValue);
                                }
                                catch (Exception e)
                                {
                                    throw new FasException(DataAccessErrorCode.StateMachine.FireBeforeSaveError, string.Format(CommonStringClassEngine.StateMachinePlugin_JudgeIfStateChangedAndExecuteBeforeSaveEvent1, invokeContext.SourceMeta.MetaDataCode), e);
                                }

                                var fireResult = StateMachineService.FireStateChanging(machine, eventKey, invokeContext.SourceData, GetExtParams());
                            }
                        }

                        //执行保存前状态机事件
                        var result = StateMachineService.FireBeforeSave(machine, eventKey, invokeContext.SourceData, GetExtParams());

                        //目标状态规则的只读字段，记录返回值
                        if (newState != null)
                        {
                            var readonlyFiledColNames = newState.Rules
                                                       .Where(r => r.RuleContents != null
                                                                //跳过包含前置条件的只读规则
                                                                && r.HasCondition == false
                                                                && r.RuleContents
                                                                    .Any(c => c.RuleType == RuleType.System
                                                                           && c.RuleInfo == "ReadOnly"))
                                                       .SelectMany(r => r.RuleFields)
                                                       .Select(f => f.ColumnName)
                                                       .ToList();
                            invokeContext.RecordData(PluginCode, PluginConst.StateMachine.CurrentRuleReadonlyFields, readonlyFiledColNames);
                        }
                    }
                    catch (Exception e)
                    {
                        var errorMsg = $"{_pluginName}{machine?.Code}执行失败，执行事件:{eventKey}保存前动作失败，Msg:{e.GetInnermostExceptionMsg()}";
                        return new ActionResult
                        {
                            Result = false,
                            ActionErrorHandleType = ActionErrorHandleType.Throw,
                            Message = errorMsg,
                        };
                    }
                }
            }
            //记录返回值
            invokeContext.RecordData(PluginCode, PluginConst.StateMachine.BeforeSavedData, invokeContext.SourceData);
            return new ActionResult
            {
                Result = true,
                ActionErrorHandleType = ActionErrorHandleType.Throw,
                ResultData = new Dictionary<string, object>
                {
                     {nameof(_stateChangeLog), _stateChangeLog }
                }
            };
        }

        /// <summary>
        /// 触发状态机的状态成功变更后事件
        /// </summary>
        /// <returns></returns>
        private ActionResult FireAfterSaveEvent(InvokeContext invokeContext, InjectionMethodType InjectMethodType, DataAccessPluginExtParam extExecuteData)
        {
            TRecord recordResult = default;
            //如果包含变更事件
            if (_stateChangeLog.Keys.Count > 0)
            {
                var cdo = invokeContext.SourceMeta as CompositeDataObject;
                List<StateMachineChangedMsg> Msg = new List<StateMachineChangedMsg>();
                foreach (var machine in _stateChangeLog.Keys)
                {
                    string eventKey = _stateChangeLog[machine];
                    try
                    {
                        if (InjectMethodType == SPI.InjectionMethodType.Update) 
                        {
                            //update操作暂定使用入参Record触发事件
                            recordResult = StateMachineService.FireAfterSave(machine, eventKey, invokeContext.SourceData, GetExtParams());
                        }
                        else
                        {
                            var record = extExecuteData?.NewRecord;
                            recordResult = StateMachineService.FireAfterSave(machine, eventKey, record, GetExtParams());
                        }
                    }
                    catch (Exception e)
                    {
                        var errorMsg = $"{_pluginName}{machine?.Code}执行失败，执行事件:{eventKey}保存后动作失败，Msg:{e.GetInnermostExceptionMsg()}";
                        return new ActionResult
                        {
                            Result = false,
                            ActionErrorHandleType = ActionErrorHandleType.Throw,
                            Message = errorMsg,
                        };
                    }
                    //构造消息
                    if (machine.EnableStateChangeEvent)
                        Msg.Add(BuildChangeMsg(cdo, machine, eventKey, recordResult));
                }
                if (Msg.Count > 0)
                    SendDataChangeLogsAsync(Msg);
            }
            return new ActionResult
            {
                Result = true,
                ResultData = new Dictionary<string, object> { { "FireStateChangedEventResult", recordResult } }
            };
        }
        
        /// <summary>
        /// 构造MQ任务消息
        /// </summary>
        /// <param name="cdo"></param>
        /// <param name="machine"></param>
        /// <param name="EventKey"></param>
        /// <param name="record"></param>
        /// <returns></returns>
        private StateMachineChangedMsg BuildChangeMsg(CompositeDataObject cdo, StateMachineMetadata machine ,string EventKey, TRecord record)
        {
            var pkCol = record.FillPkColumn(cdo.RootCompositeNodeObject).PKColumnName;
            var @do = cdo.RootCompositeNodeObject.DataObject;
            var displayName = @do.Columns.FirstOrDefault(c => c.ColumnName == machine.CDOColKey).DisplayName;
            var eventObj = machine.Events.FirstOrDefault(e => e.Key == EventKey);
            var trans = machine.Transitions.FirstOrDefault(t => t.Event.Key == EventKey);
            StateMachineChangedMsg stateMachineChangedMsg = new StateMachineChangedMsg
            {
                StateMachineID = machine.ID,
                StateMachineCode = machine.Code,
                BillID = record.GetValue(pkCol).ToString(),
                CDOCode = cdo.Code,
                ColKey = machine.CDOColKey,
                ColName = displayName,
                EventKey = EventKey,
                EventName = eventObj.Name,
                FromState = trans.From?.Key,
                FromStateName = trans.From?.Name,
                ToState = trans.To.Key,
                ToStateName = trans.To.Name,
                Record = record,
            };
            return stateMachineChangedMsg;
        }
        
        /// <summary>
        /// 异步推送消息
        /// </summary>
        /// <param name="msgs"></param>
        private void SendDataChangeLogsAsync(List<StateMachineChangedMsg> msgs)
        {
            List<ITask> tasks = new List<ITask>();
            s_localLogService.WriteStateChangeLog(msgs);
            foreach (var msg in msgs)
            {
                var taskName = $"FAS_{msg.CDOCode}_{msg.StateMachineCode}";
                var task1 = TaskBuilder.BuildAsynTask(taskName, taskName, 3);
                task1.Data.Add("StateChangeData", msg);
                tasks.Add(task1);
            }
            try
            {
                //ITaskSender sender = HSFService.Proxy<ITaskSender>();
                //var task = sender.SendBatchAsync(tasks);
                //task.Start();
            }
            catch (Exception e)
            {
                s_localLogService.WriteError(e, $"状态机推送变更成功后事件消息失败");
            }
        }

        /// <summary>
        /// 校验上下文变量正确性,true:校验通过，false:校验失败
        /// </summary>
        /// <param name="invokeContext"></param>
        /// <param name="errorResult"></param>
        /// <returns></returns>
        private bool ValidateContext(InvokeContext invokeContext, out ActionResult errorResult)
        {
            errorResult = new ActionResult
            {
                PluginCode = PluginCode,
                Result = true,
                InvokeContext = invokeContext,
                ActionErrorHandleType = ActionErrorHandleType.Throw,
            };
            if (invokeContext == null)
            {
                errorResult.Result = false;
                errorResult.Message = $"{_pluginName}执行异常,invokeContext参数为空";
            }
            var CDO = invokeContext.SourceMeta as CompositeDataObject;
            if (CDO == null)
            {
                errorResult.Result = false;
                errorResult.Message = $"{_pluginName}执行异常,invokeContext中的SoureMeta为空或类型不是CDO";
            }
            if (invokeContext.SourceData == null)
            {
                errorResult.Result = false;
                errorResult.Message = $"{_pluginName}执行异常,SourceData TRecord参数为空";
            }
            return errorResult.Result;
        }

        /// <summary>
        /// 调用运行时统一缓存服务获取状态机元数据集合
        /// </summary>
        /// <param name="cdo"></param>
        /// <returns></returns>
        private List<StateMachineMetadata> InitCDOStateMachine(CompositeDataObject cdo)
        {
            List<StateMachineMetadata> result = new List<StateMachineMetadata>();
            var machines = cdo.RootCompositeNodeObject?.Elements.Where(e => e.StateMachines != null).SelectMany(e => e.StateMachines);
            var list = machines?.Where(m => m != null).ToList();
            try
            {
                if (list != null && list.Count > 0)
                {
                    foreach (var machine in list)
                    {
                        //调统一缓存服务运行时获取StateMachine 完整元数据
                        StateMachineMetadata realMachine;
                        realMachine = RTPMetadataHelper.GetRTPMetadata(machine.ID) as StateMachineMetadata;
                        result.Add(realMachine);
                    }
                }
                return result;
            }
            catch (Exception e)
            {

                throw new FasException(DataAccessErrorCode.PlugIn.ChangeLog.Error, string.Format(CommonStringClassEngine.StateMachinePlugin_InitCDOStateMachine,_pluginName,cdo.Code), e);
            }
        }
        private bool ValidateInjectPoint(InjectionMethodType MethodType, InjectionPoint Point)
        {
            return (this.InjectionMethodType & (int)MethodType) == (int)MethodType
                      && (this.InjectionPoint & (int)Point) == (int)Point;

        }

        /// <summary>
        /// 新增或更新时，状态的新旧变更字段比较，如果返回值为空字符串，则表明没有发生状态变换
        /// **************************待改造*****************************
        /// 当前端TRecord使用ChangeSet时，record中本身可能没有state状态字段。此时应该优先根据ChangeSet中的状态字段来判断有无变更，最后再根据TRecord本身的字段判断
        /// </summary>
        private TEvent StateChangeCompare(StateMachineMetadata machine, InvokeContext invokeContext, InjectionMethodType injectMethodType, out TState newTState)
        {
            TEvent @event = default;
            newTState = null;
            if (injectMethodType == SPI.InjectionMethodType.Create)
                @event = machine.Transitions.FirstOrDefault(t => string.IsNullOrEmpty(t.FromStateID)).Event;
            else if (injectMethodType == SPI.InjectionMethodType.Update)
            {
                var meta = invokeContext.SourceMeta as CompositeDataObject;
                TRecord oldRecord;
                string pkColName = default;
                string pkData = default; //int主键也转为string处理
                try
                {
                    pkColName = meta.RootCompositeNodeObject.DataObject.Columns.FirstOrDefault(col => col.IsPkColumn).ColumnName;
                    pkData = invokeContext.SourceData.GetValue(pkColName).ToString();
                    var oldResult = DataAccessEngine.GetInstance().CreateCDOQueryService().QueryData(meta, pkData, DACControlContextHelper.BuildDefultExtParams());
                    oldRecord = ((TCommonResult<TRecord>)oldResult).Record;
                }
                catch (Exception e)
                {
                    throw new FasException(DataAccessErrorCode.PlugIn.ChangeLog.Error, string.Format(CommonStringClassEngine.StateMachinePlugin_StateChangeCompare,_pluginName,machine.Code,meta.Code,pkData), e);
                }

                try
                {
                    string stateColKey = machine.CDOColKey;

                    if (int.TryParse(oldRecord?.GetValue(stateColKey)?.ToString(), out int oldState)
                        && int.TryParse(invokeContext.SourceData?.GetValue(stateColKey)?.ToString(), out int newState))
                    {
                        TTransition transition = machine.Transitions.FirstOrDefault(t => t.From?.Key == oldState && t.To?.Key == newState);
                        @event = transition?.Event;
                        newTState = transition?.To;
                    }
                }
                catch (Exception e)
                {
                    throw new FasException(DataAccessErrorCode.PlugIn.ChangeLog.Error, string.Format(CommonStringClassEngine.StateMachinePlugin_StateChangeCompare1,_pluginName,machine.Code,meta.Code,pkData), e);
                }
            }
            return @event;
        }

        /// <summary>
        /// 构造特殊参数
        /// </summary>
        /// <returns></returns>
        private Dictionary<string, object> GetExtParams()
        {
            if (InvokeContext?.ExtProperties != null)
                return InvokeContext.ExtProperties;
            Dictionary<string, object> extParams = new Dictionary<string, object>();
            return extParams;   //现在不需要向FASContext注入ChangeSet对象
        }
        #endregion
    }
}
